/*
 * This file is part of shared-drawing.
 * shared-drawing is free software: you can redistribute it and/or modify it 
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option )
 * any later version. shared-drawing is distributed in the hope that it will
 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 * You should have received a copy of the GNU General Public License along with 
 * shared-drawing. If not, see http://www.gnu.org/licenses/.
 */

package SharedDrawing;

import common.XmlConstants;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JPanel;

public class Page extends JPanel implements MouseListener, MouseMotionListener
{
    private static final long serialVersionUID = 1L;    
    private String name;
    private Integer id;
    private List<String> views;
    private String creator;
    private int width;
    private int height;
    private BufferedImage bi;
    private Graphics2D ig2;
    
    BoundingBox previousState;
    private ArrayList<GraphicObject> objectsList;
    private GraphicObject overObject, firstPlanObject;
    private boolean isToSend;
    private int cornerType;
    private Coord[] clickLocation;
    
    public Page(Integer pageId, String name, String creator)
    {
        try
        {
            width = 550;
            height = 585;
            
            previousState = null;
            
            setBackground(Color.white);
            setOpaque(true);
            
            bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            ig2 = bi.createGraphics();
            
            this.name = name;
            this.id = pageId;
            this.creator = creator;
            views = new ArrayList<String>();
            
            objectsList = new ArrayList<GraphicObject>();
            
            overObject = firstPlanObject = null;
            cornerType = 0;
            isToSend = false;
            clickLocation = new Coord[2];
            
            for(int i = 0; i < 2; i++)
            {
                clickLocation[i] = new Coord();
            }
            setFocusable(true);
            this.addMouseListener(this);
            this.addMouseMotionListener(this);
            this.setLayout(null);
        }
        catch(Exception err)
        {
            System.out.println("Page: " + err);
        }
    }

    @Override
    public String getName() {
        return name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }
    
    public void draw(List<ShapeContent> shapes)
    {  
        for(ShapeContent shape : shapes)
        {
            try 
            {
                //the type of the shape
                String type = shape.getBoundingBox().getType();
                
                //the bounding box in the list if already exists
                GraphicObject existing = null;
                
                String ReferenceIdAsString = shape.getBoundingBox().get(XmlConstants.shapeContentReferenceId);
                
                long rId = 0;
                
                if(ReferenceIdAsString != null)
                {
                    rId = Long.parseLong(ReferenceIdAsString);
                    
                    for (GraphicObject obj : objectsList)
                    {
                        if(obj.getId() == rId)
                        {
                            existing = obj;
                        }
                    }
                }
                
                //remove a shape
                if(XmlConstants.wgContentTypeRemove.equals(type))
                {
                    if(existing == null)
                    {
                        System.out.println("removing an object that doesn't exist");
                    }
                    else
                    {
                    	objectsList.remove(existing);
                    	existing.getCurrentState().removeFromPanel();
                    }
                }
                //add a new shape
                else if (existing == null)
                {
                    BoundingBox obj = null;
                    
                    obj = Pages.getInstance().getFactory().getInstance(shape.getBoundingBox().getType());

                    if(obj == null)
                    {
                    	throw new Exception("Instanciation problem");
                    }
                    else
                    {
                        obj.initShape(this, 0, 0, 0, true, Color.black, Color.black);
                        
                        GraphicObject newObj = new GraphicObject(obj);
                        newObj.fromShapeContent(shape); 
                        
                        objectsList.add(newObj);
                    }
                }
                else //it is a changement
                {
                    shape.getBoundingBox().remove(XmlConstants.shapeContentId);
                    shape.getBoundingBox().addContent(XmlConstants.shapeContentId, 
                            shape.getBoundingBox().get(XmlConstants.shapeContentReferenceId));
                    shape.getBoundingBox().remove(XmlConstants.shapeContentReferenceId);
                    
                    // actual shape goes to the history list
                	existing.archiveState(this);
                	
                	// update of the shape
            		existing.fromShapeContent(shape);
            		
            		// pushing to the top of the top list (to paint it first)
            		objectsList.remove(existing);
                    existing.setObjectTop();
                    objectsList.add(existing);    
                }
            }
            catch (InstantiationException ex)
            {
                Logger.getLogger(Page.class.getName()).log(Level.SEVERE, null, ex);
            } 
            catch (IllegalAccessException ex) 
            {
                Logger.getLogger(Page.class.getName()).log(Level.SEVERE, null, ex);
            }
            catch (Exception ex)
            {
            	ex.printStackTrace();
            }
        }
        repaint();
    }
    
    @Override
    public void paintComponent(Graphics g)
    {
        try
        {
            super.paintComponent(g);
            
            Graphics2D g2 = (Graphics2D)g;
            
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            
            for(GraphicObject obj : objectsList)
            {
            	BoundingBox current = obj.getCurrentState();
            	
            	current.drawComponent(g2);
                
                if(obj == firstPlanObject)
                {
                	current.drawCorners(g2);
                }
            }
        }
        catch(Exception err)
        {
                System.out.println("PaintComponent: " + err);
        }
    }
    
    @Override
    public void mouseDragged(MouseEvent e)
    {
        isToSend = true;
        
        int x = (int)(e.getPoint().getX());
        int y = (int)(e.getPoint().getY());

        try
        {
            // drawing mode
            if(Pages.getInstance().getSelectionMode() == false)
            {
                if(firstPlanObject != null)
                {
                	firstPlanObject.getCurrentState().drawObject(x, y);
                    firstPlanObject.getCurrentState().setShape();
                    repaint();
                }
            }
            // select mode
            else
            {
                // change the the size of the object
                if(firstPlanObject != null && cornerType != 0)
                {    
                	firstPlanObject.getCurrentState().resizeManager(x, y, cornerType);
                    firstPlanObject.getCurrentState().setShape();

                    repaint();
                }

                // move the object
                if(overObject != null && cornerType == 0)
                {
                	overObject.getCurrentState().moveObject(x, y, clickLocation);
                    overObject.getCurrentState().setShape();
                    repaint();
                }
            }
        }
        catch(Exception err)
        {
            System.out.println("MouseDragged: " + err);
            Logger.getLogger(Page.class.getName()).log(Level.SEVERE, null, err);
        }
    }
    
    @Override
    public void mousePressed(MouseEvent e)
    {
        isToSend = false;
        long level = 0;
        
        int x = (int)(e.getPoint().getX());
        int y = (int)(e.getPoint().getY());

        
        if(e.getButton() == MouseEvent.BUTTON1)
        {
            try
            {     
                // drawing mode
                if(Pages.getInstance().getSelectionMode() == false)
                { 
                	if(firstPlanObject != null)
                        firstPlanObject.getCurrentState().removeFromPanel();
                        
                	BoundingBox obj = Pages.getInstance().getFactory().getInstance(Pages.getInstance().getPrimitiveType());

                    if(obj == null)
                    {
                        throw new Exception("Factory problem");
                    }
                    else
                    {
                    	GraphicObject newObj = new GraphicObject(obj);
                    	
                    	newObj.getCurrentState().initShape(this, x, y, 
						                                   Pages.getInstance().getPrimitiveWidth(), 
						                                   Pages.getInstance().isPrimitiveFilled(), 
						                                   Pages.getInstance().getColorSelected(), 
						                                   Pages.getInstance().getBorderColorSelected());
						
                        objectsList.add(newObj);
                        firstPlanObject = newObj;
                        previousState = null;
                    }
                }
                // select mode
                else
                {
                    cornerType = 0;
                    overObject = null;

                    // an object is select, his corners are visible
                    if(firstPlanObject != null)
                    {
                        cornerType = firstPlanObject.getCurrentState().overCorner(x, y);
                    }

                    // if the mouse is out a corner of the first plan object
                    if(cornerType == 0)
                    {
                    	for(GraphicObject obj : objectsList)
                        { 
                        	BoundingBox current = obj.getCurrentState();
                        	
                            if(current.isOverObject(x, y))
                            {
                                if(obj.getObjLevel() > level)
                                {
                                     if(firstPlanObject != null)
                                     {
                                    	 firstPlanObject.getCurrentState().removeFromPanel();
                                     }
                                         
                                    level = obj.getObjLevel();
                                    overObject = firstPlanObject = obj;
                                    previousState = firstPlanObject.getArchiveState(this);
                                    firstPlanObject.getCurrentState().addToPanel();
                                }
                            }
                        }
                    } 
                    
                    // user didn't click on an object or his corners
                    if(overObject == null && cornerType == 0)
                    {
                    	if(firstPlanObject != null)
                        {
                                firstPlanObject.getCurrentState().removeFromPanel();      
                                firstPlanObject = null;
                                repaint();
                        }
                    }

                    // if the mouse is over object but not over a corner
                    if(cornerType == 0 && overObject != null)
                    {
                        overObject.getCurrentState().setClickLocation(x, y, clickLocation);

                        // if the selected object is not the last of the list (means it is on the first plan)
                        if(overObject != objectsList.get(objectsList.size() - 1))
                        {
                            overObject.setObjectTop();

                    		objectsList.remove(overObject);
                    		objectsList.add(overObject);     
                        }
                        repaint();
                    }
                }
            }
            catch(Exception err)
            {
                err.printStackTrace();
            }
        }
        else if(e.getButton() == MouseEvent.BUTTON2)
        {
            try
            {
                if(Pages.getInstance().getSelectionMode())
                {
                	GraphicObject over = null;
                	
                	// define if the click is over a object to delete
                	for(GraphicObject obj : objectsList)
                    { 
                    	BoundingBox current = obj.getCurrentState();
                    	
                        if(current.isOverObject(x, y))
                        {
                            if(obj.getObjLevel() > level)
                            {
                                level = obj.getObjLevel();
                                over = obj;
                            }
                        }
                    }
                	
                    if(over != null && over == firstPlanObject)
                    {
                        ShapeContent remove = over.toShapeContent();
                        WgContent bounding = remove.getBoundingBox();
                        //referenced type
                        bounding.addContent(XmlConstants.shapeContentReferencedType,bounding.getType());
                        //referenced id
                        bounding.addContent(XmlConstants.shapeContentReferenceId,
                                			bounding.get(XmlConstants.shapeContentId));
                        //id
                        bounding.remove(XmlConstants.shapeContentId);
                        //old id
                        bounding.addContent(XmlConstants.shapeContentOldId, new Long(Pages.getInstance().getNextId()).toString());
                        //type
                        bounding.setType(XmlConstants.wgContentTypeRemove);
                        
                        XmlGenerator.sendXml(id, remove);
                        
                        objectsList.remove(over);
                        over.getCurrentState().removeFromPanel(); 
                        
                        repaint();
                    }
                }
            }
            catch(Exception err)
            {
                System.out.println("MousePressed - middle click: " + err);
                Logger.getLogger(Page.class.getName()).log(Level.SEVERE, null, err);
            }
        }
    }
    
    @Override
    public void mouseMoved(MouseEvent e){
    }
    
    @Override
    public void mouseClicked(MouseEvent e) {
    }
    
    @Override
    public void mouseEntered(MouseEvent e) {
    }
    
    @Override
    public void mouseExited(MouseEvent e) {
    }
    
    @Override
    public void mouseReleased(MouseEvent e) 
    {
        if(firstPlanObject != null && e.getButton() != MouseEvent.BUTTON2)
        {
            if(isToSend == true)
            {
            	if(previousState != null)
            		firstPlanObject.getPreviousStates().add(previousState);
            	
                ShapeContent shape = firstPlanObject.toShapeContent();
                shape.getBoundingBox().remove(XmlConstants.shapeContentId);

                if(Pages.getInstance().getSelectionMode())
                {
                    shape.getBoundingBox().addContent(XmlConstants.shapeContentReferenceId, 
                            						  new Long(firstPlanObject.getId()).toString());
                    
                    shape.getBoundingBox().addContent(XmlConstants.shapeContentOldId, 
                            						  new Long(Pages.getInstance().getNextId()).toString());  
                    
                    XmlGenerator.sendXml(id, shape);
                }
                else
                {
                   shape.getBoundingBox().addContent(XmlConstants.shapeContentOldId, 
                                					 String.valueOf(firstPlanObject.getOldId()));
                       
                   XmlGenerator.sendXml(id, shape);
                }
            }
            else if(Pages.getInstance().getSelectionMode() == false) // component not drawed (just a point)
            {
            	objectsList.remove(firstPlanObject);
            }
        }
    }
    
     /**
     * since all pages know who is looking at itself, this function add a user
     * in the readers list
     * @param emitter the user who opens the page
     */
    public void incPageView(String emitter) {
        views.add(emitter);
        Action action = new Action(XmlConstants.openPage);
        SharedDrawingUI.getInstance().getWorker().push(action); 
    }
    
    public void sendIncPageView() {
        views.add(ConnexionManager.getInstance().getUserId());
        Action action = new Action(XmlConstants.openPage);
        action.addContent(XmlConstants.pageId, id.toString());
        action.addContent(XmlConstants.viewerId, 
                ConnexionManager.getInstance().getUserId());
        XmlGenerator.sendXml(action);
    }

    /**
     * the opposite of incPageView
     * @param emitter the user who closes the page
     */
    public void decPageView(String emitter) {
        views.remove(emitter);
        Action action = new Action(XmlConstants.closePage);
        SharedDrawingUI.getInstance().getWorker().push(action);
    }
    
    public void sendDecPageView() {
        views.remove(ConnexionManager.getInstance().getUserId());
        Action action = new Action(XmlConstants.closePage);
        action.addContent(XmlConstants.pageId, id.toString());
        XmlGenerator.sendXml(action);
    }

    public List<String> getViews() {
        return views;
    }
    
    public List<ShapeContent> getContent()
    {
        List<ShapeContent> ret = new ArrayList<ShapeContent>();
        
        for(GraphicObject current : objectsList)
        {
            ret.add(current.toShapeContent());
        }
        return ret;
    }
    
    public void refactorShapeId(long newId, long oldId) {
        for(GraphicObject shape : objectsList)
        {
            if(shape.getOldId() == oldId)
            {
                shape.setId(newId);
            }
        }
    }
    
    /**
     * save the image in a png file
     * @param file the file to save into
     */
    public void toPNG(String pathName){
        try {
            this.paint(ig2); 
            ig2.dispose();
            ImageIO.write(bi, "png", new File(pathName));
        } catch (IOException ex) {
            System.out.println("Saving Failure.");
        }
    } 
    
    
    @Override
    public String toString()
    {
        return "name : " + name + " id " + id;
    }
    
    public void undo()
    {
        if(firstPlanObject != null)
        {
        	final int index = firstPlanObject.getPreviousStates().size() - 1;

        	if(index >= 0)
        	{
        		firstPlanObject.getNextStates().add(firstPlanObject.getCurrentState());
        		
	        	BoundingBox previousState = firstPlanObject.getPreviousStates().get(index);
	        	
	        	
	        	
	        	firstPlanObject.setCurrentState(previousState);
	        	
	        	firstPlanObject.getPreviousStates().remove(previousState);
	        	
	        	ShapeContent shape = null;
	        	
	        	shape = firstPlanObject.toShapeContent();
	        	
	        	shape.getBoundingBox().addContent(
                        XmlConstants.shapeContentReferenceId, 
                        new Long(firstPlanObject.getId()).toString());
                shape.getBoundingBox().addContent(
                        XmlConstants.shapeContentOldId, 
                        new Long(Pages.getInstance().getNextId()).toString());  
                
	        	XmlGenerator.sendXml(id, shape);
	        	
	        	repaint();
        	}
        }
    }
    
    public void redo()
    {
    	if(firstPlanObject != null)
        {
	    	int index = firstPlanObject.getNextStates().size() - 1;
	    	
	    	if(index >= 0)
	    	{
	    		firstPlanObject.getPreviousStates().add(firstPlanObject.getCurrentState());
	    		
		    	BoundingBox nextState = firstPlanObject.getNextStates().get(index);
		    	
		    	firstPlanObject.setCurrentState(nextState);
		    	
		    	firstPlanObject.getNextStates().remove(nextState);
		    	
		    	ShapeContent shape = null;
	        	
	        	shape = firstPlanObject.toShapeContent();
	        	
	        	shape.getBoundingBox().addContent(
	                    XmlConstants.shapeContentReferenceId, 
	                    new Long(firstPlanObject.getId()).toString());
	            shape.getBoundingBox().addContent(
	                    XmlConstants.shapeContentOldId, 
	                    new Long(Pages.getInstance().getNextId()).toString());  
	        	
	        	XmlGenerator.sendXml(id, shape);
	        	
	        	repaint();
	    	}
        }
    }

    void clearViewers() {
        views.clear();
    }

    String getCreator() {
        return creator;
    }

}